package org.myspringframework.beans.factory.config;

import org.myspringframework.beans.BeansException;
import org.myspringframework.beans.factory.ConfigurableListableBeanFactory;
import org.myspringframework.beans.factory.config.beandefinition.BeanDefinition;
import org.myspringframework.beans.factory.config.beandefinition.PropertyValue;
import org.myspringframework.beans.factory.config.beandefinition.PropertyValues;
import org.myspringframework.beans.factory.config.postprocessor.BeanFactoryPostProcessor;
import org.myspringframework.core.io.DefaultResourceLoader;
import org.myspringframework.core.io.Resource;
import org.myspringframework.utils.StringValueResolver;

import java.io.IOException;
import java.util.Properties;

//可以在注解上使用占位符配置一些在配置文件里的属性信息
public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {

    /**
     * Default placeholder prefix: {@value}
     */
    public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";

    /**
     * Default placeholder suffix: {@value}
     */
    public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";

    //配置文件路径
    private String location;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            // 加载属性配置文件
            DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
            Resource resource = resourceLoader.getResource(location);
            Properties properties = new Properties();
            properties.load(resource.getInputStream());

            //获取BeanDefinition
            String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
            //遍历每一个BeanDeinition
            for (String beanName : beanDefinitionNames) {
                BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);

                PropertyValues propertyValues = beanDefinition.getPropertyValues();
                //遍历Beandefinition的属性值PropertyValues
                for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
                    Object value = propertyValue.getValue();

                    //如果为引用类型(ref)，则跳过
                    if (!(value instanceof String)) continue;

                    //解析${}占位符，将其替换为配置文件中的值
                    value = resolvePlaceholder((String) value, properties);
                    propertyValue.setValue(value);
                    }
                }

                // 向容器中添加字符串解析器，供解析@Value注解使用
                StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(properties);
                beanFactory.addEmbeddedValueResolver(valueResolver);

        } catch (IOException e) {
            throw new BeansException("无法加载属性配置文件：", e);
        }
    }

    private class PlaceholderResolvingStringValueResolver implements StringValueResolver {

        //属性配置文件
        private final Properties properties;

        public PlaceholderResolvingStringValueResolver(Properties properties) {
            this.properties = properties;
        }

        @Override
        public String resolveStringValue(String strVal) {
            return PropertyPlaceholderConfigurer.this.resolvePlaceholder(strVal, properties);
        }

    }

    //解析${}占位符，将其替换为配置文件中的值
    private String resolvePlaceholder(String value, Properties properties) {
        String strVal =  value;
        StringBuilder buffer = new StringBuilder(strVal);
        //判断是否value为${}占位符
        int startIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_PREFIX);
        int stopIdx = strVal.indexOf(DEFAULT_PLACEHOLDER_SUFFIX);
        if (startIdx != -1 && stopIdx != -1 && startIdx < stopIdx) {
            //获取占位符里的值
            String propKey = strVal.substring(startIdx + 2, stopIdx);
            //吧占位符中的值作为key到配置文件中查找
            String propVal = properties.getProperty(propKey);

            //将占位符${}替换为配置文件的值
            buffer.replace(startIdx, stopIdx + 1, propVal);
            return buffer.toString();
        }
        return value;
    }

    public void setLocation(String location) {
        this.location = location;
    }

}